import argparse
import re


class Operate(object):
    def __init__(self) -> None:
        self.space: str = ' '
        self.clear_anno_regex: str = r'//[\s\S]*?(?:\n|$)'
        self.space_regex: str = r'(?:\s+\n?|\n)+'
        self.line_limit: int = 16

    def handle(self, src_file_name: str, tar_file_name: str) -> None:
        pass

    def clear_annotation(self, content: str) -> str:
        return re.sub(self.clear_anno_regex, self.space, content)


class HexBaseOperate(Operate):
    def __init__(self) -> None:
        super().__init__()


class AsciiBaseOperate(Operate):
    def __init__(self):
        super().__init__()


class CompileHex(HexBaseOperate):

    def __init__(self) -> None:
        super().__init__()

    def handle(self, src_file_name: str, tar_file_name: str) -> None:
        r_hex_f = open(src_file_name, 'r', encoding='utf-8')
        w_bin_f = open(tar_file_name, 'wb')
        buf_hex_str: str = r_hex_f.read()
        buf_hex_str = super().clear_annotation(buf_hex_str)
        buf_hex_str = buf_hex_str.strip(" ")
        hex_str_li = re.split(self.space_regex, buf_hex_str)
        for hex_str in hex_str_li:
            w_bin_f.write(int(hex_str, 16).to_bytes(1, 'big'))
        w_bin_f.flush()
        w_bin_f.close()
        r_hex_f.close()
        return None


class PrintHex(HexBaseOperate):

    def __init__(self) -> None:
        super().__init__()

    def handle(self, src_file_name: str, tar_file_name: str) -> None:
        r_bin_f = open(src_file_name, 'rb')
        w_hex_f = open(tar_file_name, 'w')
        current_col_index: int = 0
        for b in r_bin_f.read():
            if current_col_index >= self.line_limit:
                w_hex_f.write('\n')
                current_col_index = 0
            hex_str: str = hex(b)[2:].rjust(2, '0')
            w_hex_f.write('{hex}{space}'.format(hex=hex_str, space=self.space))
            current_col_index += 1
        r_bin_f.close()
        w_hex_f.flush()
        w_hex_f.close()
        return None


class PrintAscii(AsciiBaseOperate):

    def __init__(self):
        super().__init__()

    def handle(self, src_file_name: str, tar_file_name: str) -> None:
        r_bin_f = open(src_file_name, 'rb')
        w_ascii_f = open(tar_file_name, 'w')
        current_col_index: int = 0
        for b in r_bin_f.read():
            if current_col_index >= self.line_limit:
                w_ascii_f.write('\n')
                current_col_index = 0
            c_str: str
            if 33 <= b <= 126:
                c_str = b.to_bytes(1, 'big').decode('ascii').rjust(2, " ")
            else:
                c_str = hex(b)[2:].rjust(2, '0')
            w_ascii_f.write('{c_str}{space}'.format(c_str=c_str, space=self.space))
            current_col_index += 1
        r_bin_f.close()
        w_ascii_f.flush()
        w_ascii_f.close()
        return None


class CompileAscii(AsciiBaseOperate):

    def __init__(self):
        super().__init__()

    def handle(self, src_file_name: str, tar_file_name: str) -> None:
        r_ascii_f = open(src_file_name, 'r', encoding='utf-8')
        w_bin_f = open(tar_file_name, 'wb')
        buf_ascii_str: str = r_ascii_f.read()
        buf_ascii_str = super().clear_annotation(buf_ascii_str)
        buf_ascii_str = buf_ascii_str.strip()
        ascii_str_li: list = re.split(self.space_regex, buf_ascii_str)
        for cb in ascii_str_li:
            if len(cb) == 1:
                w_bin_f.write(str(cb).encode("ascii"))
            else:
                w_bin_f.write(int(cb, 16).to_bytes(1, 'big'))
        w_bin_f.flush()
        w_bin_f.close()
        r_ascii_f.close()
        return None


class OperatorFactory:
    def __init__(self):
        self.type_di = {'a': 'PrintHex', 'c': 'CompileHex', 'pa': 'PrintAscii', 'ca': 'CompileAscii'}

    def get_type(self, op: str) -> str:
        type_name: str = self.type_di[op]
        if type_name is None:
            raise Exception("No Such Operator: " + op)
        return type_name

    def create(self, op: str) -> Operate:
        type_name = self.get_type(op)
        result: Operate = globals()[type_name]()
        return result


if __name__ == '__main__':
    factory: OperatorFactory = OperatorFactory()

    option_defined_name: str = 'op'
    src_defined_name: str = 'src'
    tar_defined_name: str = 'tar'

    parser: argparse = argparse.ArgumentParser()
    parser.add_argument('--{}'.format(option_defined_name), help='option: c, p, ca, pa', required=True)
    parser.add_argument('--{}'.format(src_defined_name), help="source file", required=True)
    parser.add_argument('--{}'.format(tar_defined_name), help='target file', required=True)
    args: argparse.Namespace = parser.parse_args()

    op_str: str = getattr(args, option_defined_name)
    src_file: str = getattr(args, src_defined_name)
    tar_file: str = getattr(args, tar_defined_name)

    op_instance: Operate = factory.create(op_str)
    op_instance.handle(src_file, tar_file)
